﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace PENet
{
    public class KCPClient<TSession> where TSession : KCPSession, new()
    {
        UdpClient udp;
        IPEndPoint remotePoint;

        private CancellationTokenSource cts;
        private CancellationToken ct;

        public KCPClient()
        {
            cts = new CancellationTokenSource();
            ct = cts.Token;
        }

        public TSession session;
        public void Start(string ip, int port)
        {
            udp = new UdpClient(0);
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                udp.Client.IOControl((IOControlCode)(-1744830452), new byte[] { 0, 0, 0, 0 }, null);
            }
            remotePoint = new IPEndPoint(IPAddress.Parse(ip), port);
            KCPTool.ColorLog(KCPLogColor.Green, "Client Start...");

            Task.Run(ClientRecive, ct);
        }

        async void ClientRecive()
        {
            UdpReceiveResult result;
            while (true)
            {
                try
                {
                    if (ct.IsCancellationRequested)
                    {
                        KCPTool.ColorLog(KCPLogColor.Cyan, "ClientRecive Task is Cancelled.");
                        break;
                    }

                    result = await udp.ReceiveAsync();
                    if (Equals(remotePoint, result.RemoteEndPoint))
                    {
                        uint sid = BitConverter.ToUInt32(result.Buffer, 0);
                        if (sid == 0)
                        {
                            //初始化
                            if (IsConnectServer)
                            {
                                //已经建立连接，初始化完成了，收到了多的sid,直接丢弃。
                                KCPTool.Warn("Client is Init Done,Sid Surplus.");
                            }
                            else
                            {
                                //未初始化，收到服务器分配的sid数据，初始化一个客户端session
                                sid = BitConverter.ToUInt32(result.Buffer, 4);
                                KCPTool.ColorLog(KCPLogColor.Green, $"UDP Init Sid:{sid}");

                                // 新建会话
                                session = new TSession();
                                session.InitSession(sid, SendUDPMsg, remotePoint);
                                session.OnSessionClose = OnClientSessionClose;
                            }
                        }
                        else
                        {
                            //处理业务逻辑
                            if (session != null && session.IsConnected)
                            {
                                session.ReciveData(result.Buffer);
                            }
                            else
                            {
                                //没初始化且sid!=0，数据消息提前到了，直接丢弃消息，直到初
                                //始化完成，kcp重传再开始处理。
                                KCPTool.Warn("Client is on Initing...So Miss this Data");
                            }
                        }
                    }
                    else
                    {
                        KCPTool.Warn("Client Recive's EndPoint Wrong>>" +
                            $"Address:{remotePoint.Address} Port:{remotePoint.Port}");
                    }
                }
                catch (Exception e)
                {
                    KCPTool.Warn($"Client Udp Recive Data Exception:{e}");
                }
            }
        }

        public void ConnectServer(int interval = 200, int maxInterval = 5000, int maxTimes = 4)
        {
            Task.Run(async () =>
            {
                int connectCounts = 0;
                while (true)
                {
                    bool success = await TryConnectServer(interval, maxInterval);
                    if (success)
                    {
                        KCPTool.ColorLog(KCPLogColor.Green, "ConnectServer Success.");
                        break;
                    }
                    if (++connectCounts >= 4)
                    {
                        KCPTool.Warn($"Connect Failed {connectCounts} Times,Check Your Network.");
                        break;
                    }
                    KCPTool.Warn($"Connect Faild {connectCounts} Times.Retry...");
                    await Task.Delay(3000);
                }
            });
        }

        public async Task<bool> AsyncConnectServer(int interval = 200, int maxInterval = 5000, int maxTimes = 4)
        {
            int connectCounts = 0;
            while (true)
            {
                bool success = await TryConnectServer(interval, maxInterval);
                if (success)
                {
                    KCPTool.ColorLog(KCPLogColor.Green, "ConnectServer Success.");
                    return true;
                }
                if (++connectCounts >= 4)
                {
                    KCPTool.Warn($"Connect Failed {connectCounts} Times,Check Your Network.");
                    return false;
                }
                KCPTool.Warn($"Connect Faild {connectCounts} Times.Retry...");
                await Task.Delay(3000);
            }
        }

        public async Task<bool> TryConnectServer(int interval, int maxInterval)
        {
            SendUDPMsg(new byte[4], remotePoint);
            int checkTimes = 0;
            while (true)
            {
                await Task.Delay(interval);
                checkTimes += interval;
                if (IsConnectServer)
                    return true;
                if (checkTimes > maxInterval)
                    return false;
            }
        }

        public void CloseClient()
        {
            if (session != null)
            {
                session.CloseSession();
            }
        }

        void OnClientSessionClose(uint sid)
        {
            cts.Cancel();
            udp?.Close();
            udp = null;
            KCPTool.Warn($"Client Session Close,sid:{sid}");
        }

        void SendUDPMsg(byte[] bytes, IPEndPoint remotePoint)
            => udp?.SendAsync(bytes, bytes.Length, remotePoint);


        public bool IsConnectServer => session != null && session.IsConnected;
        public void SendMsg(byte[] msg) => session.SendMsg(msg);
    }
}

